/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_macros.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_date.h"
#include "plugins/ecmascript/runtime/js_date_time_format.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/js_object-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/tagged_array.h"

namespace ark::ecmascript::builtins {

// definition for set data code.
static constexpr uint32_t CODE_SET_DATE = 0x32;
static constexpr uint32_t CODE_SET_MILLISECONDS = 0x76;
static constexpr uint32_t CODE_SET_SECONDS = 0x75;
static constexpr uint32_t CODE_SET_MINUTES = 0x74;
static constexpr uint32_t CODE_SET_HOURS = 0x73;
static constexpr uint32_t CODE_SET_MONTH = 0x31;
static constexpr uint32_t CODE_SET_FULL_YEAR = 0x30;
static constexpr uint8_t CONSTRUCTOR_MAX_LENGTH = 7;
// constructor
JSTaggedValue date::DateConstructor(EcmaRuntimeCallInfo *argv)
{
    BUILTINS_API_TRACE(argv->GetThread(), Date, DateConstructor);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> newTarget = builtins_common::GetNewTarget(argv);
    if (newTarget->IsUndefined()) {
        double now = JSDate::Now().GetDouble();
        PandaString str = JSDate::ToDateString(now);
        return builtins_common::GetTaggedString(thread, str.c_str());
    }

    JSTaggedValue timeValue(0.0);
    uint32_t length = argv->GetArgsNumber();
    if (length == 0) {  // no value
        timeValue = JSDate::Now();
    } else if (length == 1) {  // one value
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, 0);
        if (value->IsDate()) {  // The value is a date object.
            JSHandle<JSDate> jsDate(thread, JSDate::Cast(value->GetTaggedObject()));
            timeValue = jsDate->GetTimeValue();
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        } else {
            JSHandle<JSTaggedValue> objValue(thread, JSTaggedValue::ToPrimitive(thread, value));
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            if (objValue->IsString()) {  // The value is a string object.
                timeValue = JSDate::Parse(argv);
            } else {  // The value is a number.
                JSTaggedNumber val = JSTaggedValue::ToNumber(thread, objValue);
                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
                timeValue = JSTaggedValue(val.GetNumber());
            }
            timeValue = JSTaggedValue(JSDate::TimeClip(timeValue.GetDouble()));
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        }
    } else {  // two or more values
        std::array<int64_t, DATE_LENGTH> fields = {0, 0, 1, 0, 0, 0, 0, 0, 0};
        if (length > CONSTRUCTOR_MAX_LENGTH) {  // The max length is 7.
            length = CONSTRUCTOR_MAX_LENGTH;
        }
        uint32_t i = 0;
        for (; i < length; ++i) {
            JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, i);
            JSTaggedNumber res = JSTaggedValue::ToNumber(thread, value);
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            double temp = res.GetNumber();
            if (std::isnan(temp) || !std::isfinite(temp)) {  // Check the double value is finite.
                break;
            }
            fields[i] = static_cast<int64_t>(temp);
            if (i == 0 && fields[0] >= 0 && fields[0] < JSDate::HUNDRED) {
                fields[0] += JSDate::NINETEEN_HUNDRED_YEAR;
            }
        }
        timeValue = JSTaggedValue((i == length) ? JSDate::SetDateValues(&fields, true) : ecmascript::base::NAN_VALUE);
    }

    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> constructor = builtins_common::GetConstructor(argv);
    JSHandle<JSDate> dateObject =
        JSHandle<JSDate>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    dateObject->SetTimeValue(thread, timeValue);
    return JSTaggedValue(JSObject::Cast(static_cast<TaggedObject *>(*dateObject)));
}

// 20.4.3.1
JSTaggedValue date::Now([[maybe_unused]] EcmaRuntimeCallInfo *argv)
{
    BUILTINS_API_TRACE(argv->GetThread(), Date, Now);
    return JSDate::Now();
}

// 20.4.3.2
JSTaggedValue date::Parse(EcmaRuntimeCallInfo *argv)
{
    BUILTINS_API_TRACE(argv->GetThread(), Date, Parse);
    [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
    return JSDate::Parse(argv);
}

// 20.4.3.4
JSTaggedValue date::UTC(EcmaRuntimeCallInfo *argv)
{
    BUILTINS_API_TRACE(argv->GetThread(), Date, UTC);
    [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
    return JSDate::UTC(argv);
}

// B.2.3.1
JSTaggedValue date::proto::GetYear(EcmaRuntimeCallInfo *argv)
{
    double result = GetFullYear(argv).GetNumber() - ark::ecmascript::JSDate::NINETEEN_HUNDRED_YEAR;
    return builtins_common::GetTaggedDouble(result);
}

// 20.4.4.10
JSTaggedValue date::proto::GetTime(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), DatePrototype, GetTime);
    JSThread *thread = argv->GetThread();
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    return JSDate::Cast(msg->GetTaggedObject())->GetTime();
}

JSTaggedValue date::proto::SetTime(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), DatePrototype, SetTime);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    JSHandle<JSDate> jsData(thread, JSDate::Cast(msg->GetTaggedObject()));
    JSTaggedNumber res = JSTaggedValue::ToNumber(thread, builtins_common::GetCallArg(argv, 0));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(argv->GetThread());
    double number = res.GetNumber();
    double value = JSDate::TimeClip(number);
    jsData->SetTimeValue(thread, JSTaggedValue(value));
    return builtins_common::GetTaggedDouble(value);
}

// 20.4.4.37
JSTaggedValue date::proto::ToJSON(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), DatePrototype, ToJSON);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // 1. Let O be ToObject(this value).
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    JSHandle<JSObject> object = JSTaggedValue::ToObject(thread, msg);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 2. Let tv be ToPrimitive(hint Number)
    JSHandle<JSTaggedValue> objectHandle = JSHandle<JSTaggedValue>::Cast(object);
    JSHandle<JSTaggedValue> tv(thread,
                               JSTaggedValue::ToPrimitive(thread, objectHandle, PreferredPrimitiveType::PREFER_NUMBER));

    // 3. If Type(tv) is Number and tv is not finite, return null
    if (tv->IsNumber()) {
        if (tv->IsDouble() && !std::isfinite(tv->GetDouble())) {
            return JSTaggedValue::Null();
        }
    }
    JSHandle<JSTaggedValue> calleeKey(thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("toISOString"));
    auto info = NewRuntimeCallInfo(thread, JSTaggedValue::Undefined(), objectHandle, JSTaggedValue::Undefined(), 0);
    return JSFunction::Invoke(info.Get(), calleeKey);
}

// 20.4.4.44
JSTaggedValue date::proto::ValueOf(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), DatePrototype, ValueOf);
    JSThread *thread = argv->GetThread();
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        [[maybe_unused]] EcmaHandleScope handleScope(thread);
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    return JSDate::Cast(msg->GetTaggedObject())->ValueOf();
}

// 20.4.4.45
JSTaggedValue date::proto::ToPrimitive(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), DatePrototype, ToPrimitive);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> object = builtins_common::GetThis(argv);
    if (!object->IsECMAObject()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a JSObject", JSTaggedValue::Exception());
    }
    JSHandle<JSTaggedValue> hint = builtins_common::GetCallArg(argv, 0);
    PreferredPrimitiveType tryFirst = PREFER_STRING;
    if (hint->IsString()) {
        JSHandle<EcmaString> numberStrHandle = factory->NewFromCanBeCompressString("number");
        if (EcmaString::StringsAreEqual(hint.GetObject<EcmaString>(), *numberStrHandle)) {
            tryFirst = PREFER_NUMBER;
        } else {
            JSHandle<EcmaString> stringStrHandle = factory->NewFromCanBeCompressString("string");
            JSHandle<EcmaString> defaultStrHandle = factory->NewFromCanBeCompressString("default");
            if (EcmaString::StringsAreEqual(hint.GetObject<EcmaString>(), *stringStrHandle) ||
                EcmaString::StringsAreEqual(hint.GetObject<EcmaString>(), *defaultStrHandle)) {
                tryFirst = PREFER_STRING;
            } else {
                THROW_TYPE_ERROR_AND_RETURN(thread, "This is not a primitiveType.", JSTaggedValue::Exception());
            }
        }
    } else {
        THROW_TYPE_ERROR_AND_RETURN(thread, "This is not an primitiveType.", JSTaggedValue::Exception());
    }
    return JSTaggedValue::OrdinaryToPrimitive(thread, object, tryFirst);
}

// ecma 402 16.4.1 Date.prototype.toLocaleString ( [ locales [ , options ] ] )
JSTaggedValue date::proto::ToLocaleString(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // Let x be ? thisTimeValue(this value).
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    JSTaggedValue value = JSDate::Cast(msg->GetTaggedObject())->GetTime();
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If x is NaN, return "Invalid Date".
    double x = value.GetNumber();
    if (std::isnan(x)) {
        return thread->GlobalConstants()->GetInvalidDateString();
    }

    // Let options be ? ToDateTimeOptions(options, "any", "all").
    JSHandle<JSTaggedValue> locales = builtins_common::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> options = builtins_common::GetCallArg(argv, 1);
    JSHandle<JSObject> dateTimeOptions =
        JSDateTimeFormat::ToDateTimeOptions(thread, options, RequiredOption::ANY, DefaultsOption::ALL);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
    JSHandle<JSTaggedValue> ctor = env->GetDateTimeFormatFunction();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor);
    JSHandle<JSDateTimeFormat> dtf = JSDateTimeFormat::InitializeDateTimeFormat(
        thread, JSHandle<JSDateTimeFormat>::Cast(obj), locales, JSHandle<JSTaggedValue>::Cast(dateTimeOptions));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Return ? FormatDateTime(dateFormat, x).
    JSHandle<EcmaString> result = JSDateTimeFormat::FormatDateTime(thread, dtf, x);
    return result.GetTaggedValue();
}

// ecma 402 16.4.1 Date.prototype.toLocaleString ( [ locales [ , options ] ] )
JSTaggedValue date::proto::ToLocaleDateString(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // Let x be ? thisTimeValue(this value).
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    JSTaggedValue value = JSDate::Cast(msg->GetTaggedObject())->GetTime();
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If x is NaN, return "Invalid Date".
    double x = value.GetNumber();
    if (std::isnan(x)) {
        return thread->GlobalConstants()->GetInvalidDateString();
    }

    // Let options be ? ToDateTimeOptions(options, "any", "all").
    JSHandle<JSTaggedValue> locales = builtins_common::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> options = builtins_common::GetCallArg(argv, 1);
    JSHandle<JSObject> dateTimeOptions =
        JSDateTimeFormat::ToDateTimeOptions(thread, options, RequiredOption::DATE, DefaultsOption::DATE);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
    JSHandle<JSTaggedValue> ctor = env->GetDateTimeFormatFunction();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor);
    JSHandle<JSDateTimeFormat> dtf = JSDateTimeFormat::InitializeDateTimeFormat(
        thread, JSHandle<JSDateTimeFormat>::Cast(obj), locales, JSHandle<JSTaggedValue>::Cast(dateTimeOptions));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Return ? FormatDateTime(dateFormat, x).
    JSHandle<EcmaString> result = JSDateTimeFormat::FormatDateTime(thread, dtf, x);
    return result.GetTaggedValue();
}

// ecma 402 16.4.1 Date.prototype.toLocaleString ( [ locales [ , options ] ] )
JSTaggedValue date::proto::ToLocaleTimeString(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);

    // Let x be ? thisTimeValue(this value).
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    JSTaggedValue value = JSDate::Cast(msg->GetTaggedObject())->GetTime();
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // If x is NaN, return "Invalid Date".
    double x = value.GetNumber();
    if (std::isnan(x)) {
        return thread->GlobalConstants()->GetInvalidDateString();
    }

    // Let options be ? ToDateTimeOptions(options, "any", "all").
    JSHandle<JSTaggedValue> locales = builtins_common::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> options = builtins_common::GetCallArg(argv, 1);
    JSHandle<JSObject> dateTimeOptions =
        JSDateTimeFormat::ToDateTimeOptions(thread, options, RequiredOption::TIME, DefaultsOption::TIME);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
    JSHandle<JSTaggedValue> ctor = env->GetDateTimeFormatFunction();
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor);
    JSHandle<JSDateTimeFormat> dtf = JSDateTimeFormat::InitializeDateTimeFormat(
        thread, JSHandle<JSDateTimeFormat>::Cast(obj), locales, JSHandle<JSTaggedValue>::Cast(dateTimeOptions));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // Return ? FormatDateTime(dateFormat, x).
    JSHandle<EcmaString> result = JSDateTimeFormat::FormatDateTime(thread, dtf, x);
    return result.GetTaggedValue();
}

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define GET_DATE_VALUE(name, code, isLocal)                                                       \
    JSTaggedValue date::proto::name(EcmaRuntimeCallInfo *argv)                                    \
    {                                                                                             \
        ASSERT(argv);                                                                             \
        JSThread *thread = argv->GetThread();                                                     \
        JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);                             \
        if (!msg->IsDate()) {                                                                     \
            THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception()); \
        }                                                                                         \
        JSHandle<JSDate> jsDate = JSHandle<JSDate>(msg);                                          \
        double result = jsDate->GetDateValue(jsDate->GetTimeValue().GetDouble(), code, isLocal);  \
        return builtins_common::GetTaggedDouble(result);                                          \
    }

// 20.4.4.2 Date.prototype.getDate ( )
GET_DATE_VALUE(GetDate, DAYS, true)

// 20.4.4.3 Date.prototype.getDay ( )
GET_DATE_VALUE(GetDay, WEEKDAY, true)

// 20.4.4.4 Date.prototype.getFullYear ( )
GET_DATE_VALUE(GetFullYear, YEAR, true)

// 20.4.4.5 Date.prototype.getHours ( )
GET_DATE_VALUE(GetHours, HOUR, true)

// 20.4.4.6 Date.prototype.getMilliseconds ( )
GET_DATE_VALUE(GetMilliseconds, MS, true)

// 20.4.4.7 Date.prototype.getMinutes ( )
GET_DATE_VALUE(GetMinutes, MIN, true)

// 20.4.4.8 Date.prototype.getMonth ( )
GET_DATE_VALUE(GetMonth, MONTH, true)

// 20.4.4.9 Date.prototype.getSeconds ( )
GET_DATE_VALUE(GetSeconds, SEC, true)

// 20.4.4.11 Date.prototype.getTimezoneOffset ( )
GET_DATE_VALUE(GetTimezoneOffset, TIMEZONE, true)

// 20.4.4.12 Date.prototype.getUTCDate ( )
GET_DATE_VALUE(GetUTCDate, DAYS, false)

// 20.4.4.13 Date.prototype.getUTCDay ( )
GET_DATE_VALUE(GetUTCDay, WEEKDAY, false)

// 20.4.4.14 Date.prototype.getUTCFullYear ( )
GET_DATE_VALUE(GetUTCFullYear, YEAR, false)

// 20.4.4.15 Date.prototype.getUTCHours ( )
GET_DATE_VALUE(GetUTCHours, HOUR, false)

// 20.4.4.16 Date.prototype.getUTCMilliseconds ( )
GET_DATE_VALUE(GetUTCMilliseconds, MS, false)

// 20.4.4.17 Date.prototype.getUTCMinutes ( )
GET_DATE_VALUE(GetUTCMinutes, MIN, false)

// 20.4.4.18 Date.prototype.getUTCMonth ( )
GET_DATE_VALUE(GetUTCMonth, MONTH, false)

// 20.4.4.19 Date.prototype.getUTCSeconds ( )
GET_DATE_VALUE(GetUTCSeconds, SEC, false)
#undef GET_DATE_VALUE

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SET_DATE_VALUE(name, code, isLocal)                                                       \
    JSTaggedValue date::proto::name(EcmaRuntimeCallInfo *argv)                                    \
    {                                                                                             \
        ASSERT(argv);                                                                             \
        JSThread *thread = argv->GetThread();                                                     \
        JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);                             \
        if (!msg->IsDate()) {                                                                     \
            THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception()); \
        }                                                                                         \
        JSHandle<JSDate> jsDate = JSHandle<JSDate>(msg);                                          \
        JSTaggedValue result = jsDate->SetDateValue(argv, code, isLocal);                         \
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);                                            \
        jsDate->SetTimeValue(thread, result);                                                     \
        return result;                                                                            \
    }

// 20.3.4.20 Date.prototype.setDate ( date )
SET_DATE_VALUE(SetDate, CODE_SET_DATE, true)

// 20.3.4.21 Date.prototype.setFullYear ( year [ , month [ , date ] ] )
SET_DATE_VALUE(SetFullYear, CODE_SET_FULL_YEAR, true)

// 20.3.4.22 Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] )
SET_DATE_VALUE(SetHours, CODE_SET_HOURS, true)

// 20.3.4.23 Date.prototype.setMilliseconds ( ms )
SET_DATE_VALUE(SetMilliseconds, CODE_SET_MILLISECONDS, true)

// 20.3.4.24 Date.prototype.setMinutes ( min [ , sec [ , ms ] ] )
SET_DATE_VALUE(SetMinutes, CODE_SET_MINUTES, true)

// 20.3.4.25 Date.prototype.setMonth ( month [ , date ] )
SET_DATE_VALUE(SetMonth, CODE_SET_MONTH, true)

// 20.3.4.26 Date.prototype.setSeconds ( sec [ , ms ] )
SET_DATE_VALUE(SetSeconds, CODE_SET_SECONDS, true)

// 20.3.4.28 Date.prototype.setUTCDate ( date )
SET_DATE_VALUE(SetUTCDate, CODE_SET_DATE, false)

// 20.3.4.29 Date.prototype.setUTCFullYear ( year [ , month [ , date ] ] )
SET_DATE_VALUE(SetUTCFullYear, CODE_SET_FULL_YEAR, false)

// 20.3.4.30 Date.prototype.setUTCHours ( hour [ , min [ , sec [ , ms ] ] ] )
SET_DATE_VALUE(SetUTCHours, CODE_SET_HOURS, false)

// 20.3.4.31 Date.prototype.setUTCMilliseconds ( ms )
SET_DATE_VALUE(SetUTCMilliseconds, CODE_SET_MILLISECONDS, false)

// 20.3.4.32 Date.prototype.setUTCMinutes ( min [ , sec [, ms ] ] )
SET_DATE_VALUE(SetUTCMinutes, CODE_SET_MINUTES, false)

// 20.3.4.33 Date.prototype.setUTCMonth ( month [ , date ] )
SET_DATE_VALUE(SetUTCMonth, CODE_SET_MONTH, false)

// 20.3.4.34 Date.prototype.setUTCSeconds ( sec [ , ms ] )
SET_DATE_VALUE(SetUTCSeconds, CODE_SET_SECONDS, false)

#undef SET_DATE_VALUE

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define DATE_STRING(name)                                                                                          \
    JSTaggedValue date::proto::name(EcmaRuntimeCallInfo *argv)                                                     \
    {                                                                                                              \
        ASSERT(argv);                                                                                              \
        JSThread *thread = argv->GetThread();                                                                      \
        [[maybe_unused]] EcmaHandleScope handle_scope(thread);                                                     \
        JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);                                              \
        if (!msg->IsDate()) {                                                                                      \
            THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());                  \
        }                                                                                                          \
        if (std::isnan(JSDate::Cast(msg->GetTaggedObject())->GetTimeValue().GetDouble())) {                        \
            return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString("Invalid Date").GetTaggedValue(); \
        }                                                                                                          \
        return JSDate::Cast(msg->GetTaggedObject())->name(thread);                                                 \
    }
// 20.4.4.35 Date.prototype.toDateString ( )
DATE_STRING(ToDateString)

// 20.4.4.41 Date.prototype.toString ( )
DATE_STRING(ToString)

// 20.4.4.42 Date.prototype.toTimeString ( )
DATE_STRING(ToTimeString)

// 20.4.4.43 Date.prototype.toUTCString ( )
DATE_STRING(ToUTCString)

#undef DATE_STRING

// 20.4.4.36 Date.prototype.toISOString ( )
JSTaggedValue date::proto::ToISOString(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> msg = builtins_common::GetThis(argv);
    if (!msg->IsDate()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Not a Date Object", JSTaggedValue::Exception());
    }
    if (std::isnan(JSDate::Cast(msg->GetTaggedObject())->GetTimeValue().GetDouble())) {
        THROW_RANGE_ERROR_AND_RETURN(thread, "range error", JSTaggedValue::Exception());
    }
    return JSDate::Cast(msg->GetTaggedObject())->ToISOString(thread);
}

}  // namespace ark::ecmascript::builtins
